/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		auto_array.cpp
 * PURPOSE:		automatic (C++ semantics) allocation, copy and freeing of a self-sizing POD block
 */

/* auto_szpod: AUTOmatic self-SiZed block of Plain Old Data

Contains or creates a heap allocated pointer to a POD structure of variable size, providing value semantics.
Copy and assignment operations make a new copy of the source data as expected if the element was passed by value.
auto_szpod can be safely used in a container that follows standard C++.

Element type (T) requirements:
	T must be able to detirmine its own size in bytes by calling a specialized version of auto_szpod_length.
	T must not have a non-trivial constructor: new objects are zeroed and copying is accomplised by memcpy.
	T must not own pointers to other data-- these will simply be copied along with the element.

default_pod_allocator is the default allocator; it treats T as a binary blob with no C++ semantics. See its notes.
	T must contain only plain data that retains its meaning regardless of where it is stored.
	T must not require construction or destruction; any constructor or destructor will NOT be called.

auto_szpod is well suited to C structures that have a variable size.
since auto_szpod with the default allocator contains only a (T *), it should be binarily compatible with a (T *).

auto_szpod can be automatically converted to and from (T *); converting to makes a copy, from returns the internal pointer.
release() returns the stored pointer and clears the interal pointer-- effecively transferring ownership away.
take(T *) frees any stored pointer and takes ownership of the new pointer.
Note that when transferring ownership of a pointer, the allocator must be the same for both parties.

alloc(size_t) frees any stored pointer and replaces it with a zero-initiailized buffer of size_t bytes
alloc_ct(size_t) is the same, except size_t is multiplied by sizeof(T)

assign_indirect frees any stored pointer, then returns a pointer to the stored pointer so that it can be re-assigned
Use this function for functions that return a self-allocated value via a parameter.
Take care that it allocates the pointer using the same allocator that this instance of auto_szpod does.

Special constructors:

auto_szpod can be constructed with ownership of an existing pointer with auto_szpod_take, e.g.
	auto_szpod<ACE> Ace = auto_szpod_take(PreviouslyAllocated);
A copy is not made in this case, and auto_szpod will free it when destructed.

auto_szpod_empty(size_t) starts as though alloc(size_t) was called
auto_szpod_empty_ct(size_t) starts as though alloc_ct(size_t) was called
*/

template <class T> size_t auto_szpod_length(T const *);

struct auto_szpod_empty_obj //for special constructor, start with an empty buffer of newlength bytes
{
	size_t newlength;
	auto_szpod_empty_obj(size_t newlength) :
		newlength(newlength)
	{}
};

FORCEINLINE auto_szpod_empty_obj auto_szpod_empty(size_t newlength)
{
	return auto_szpod_empty_obj(newlength);
}

struct auto_szpod_empty_ct_obj //for special constructor, start with an empty buffer of newlength * sizeof(T) bytes
{
	size_t newlength;
	auto_szpod_empty_ct_obj(size_t newlength) :
		newlength(newlength)
	{}
};

FORCEINLINE auto_szpod_empty_ct_obj auto_szpod_empty_ct(size_t newlength)
{
	return auto_szpod_empty_ct_obj(newlength);
}

template <class T, template<class> class this_allocator = default_pod_allocator> class auto_szpod : this_allocator<T>::type
{
	T *ptr;

	void free_ptr_if()
	{
		if(ptr)
			free(ptr);

		ptr = 0;
	}
	void copy_set(T const *x)
	{
		if(x == 0)
			return;
		size_t length = auto_szpod_length(x);
		ptr = alloc(length);
		memcpy(c_cast<void *>(ptr), x, length);
	}
	void set_empty(size_t newlength)
	{
		ptr = alloc(newlength);
		memset(ptr, 0, newlength);
	}
public:
	auto_szpod() :
	  ptr(0)
	{}
	auto_szpod(auto_szpod const &x) :
		ptr(0)
	{
		copy_set(x.ptr);
	}
	auto_szpod(T const *x) :
		ptr(0)
	{
		copy_set(x);
	}
	auto_szpod(auto_take<T> src) : //special construcor in which auto_szpod takes ownership of src's ptr
		ptr(src.ptr)
	{}
	auto_szpod(auto_szpod_empty_obj newlength) :
		ptr(0)
	{
		set_empty(newlength.newlength);
	}
	auto_szpod(auto_szpod_empty_ct_obj newlength) :
		ptr(0)
	{
		set_empty(newlength.newlength * sizeof(T));
	}
	void realloc(size_t newlength)
	{
		free_ptr_if();
		set_empty(newlength);
	}
	void realloc_ct(size_t newcount)
	{
		free_ptr_if();
		set_empty(newcount * sizeof(T));
	}
	auto_take<T> release()
	{
		T *ret = ptr;
		ptr = 0;
		return auto_take<T>(ret);
	}
	T *release_unsafe()
	{
		T *ret = ptr;
		ptr = 0;
		return ret;
	}
	void reset()
	{
		free_ptr_if();
	}
	void take(T *x)
	{
		free_ptr_if();
		ptr = x;
	}
	T const *get() const
	{
		return ptr;
	}
	T *get()
	{
		return ptr;
	}
	T **assign_indirect()
	{
		free_ptr_if();
		return &ptr;
	}
	size_t length()
	{
		if(ptr)
			return auto_szpod_length<T>(ptr);
		else
			return 0;
	}
	operator T const *() const
	{
		return ptr;
	}
	T const *operator->() const
	{
		return ptr;
	}
	operator T *()
	{
		return ptr;
	}
	T *operator->()
	{
		return ptr;
	}
	auto_szpod &operator =(auto_szpod const &x)
	{
		free_ptr_if();
		copy_set(x.ptr);
		return *this;
	}
	template <class U> auto_szpod &operator =(auto_szpod<U> const &x)
	{
		free_ptr_if();
		copy_set(x.ptr);
		return *this;
	}
	template <class U> auto_szpod &operator = (auto_take<U> const &x)
	{
		free_ptr_if();
		ptr = x.ptr;
		return *this;
	}
	auto_szpod &operator =(T const *x)
	{
		free_ptr_if();
		copy_set(x);
		return *this;
	}
	~auto_szpod()
	{
		free_ptr_if();
	}
};